package com.stormragetech.arthas.serialize;

import com.stormragetech.arthas.codec.ArthasHttpRequest;
import com.stormragetech.arthas.common.annotation.PathParam;
import com.stormragetech.arthas.common.constant.Constant;
import com.stormragetech.arthas.common.exception.ArthasException;
import com.stormragetech.arthas.common.util.Assert;
import com.stormragetech.arthas.serialize.convertor.*;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * description: http request serialize handeler implements ArthasSerialize
 *
 * @author: Xue Bin
 * @version: 1.0.0-SNAPSHOT
 */
public class ArthasHttpRequestSerializeHandler implements ArthasSerialize {

    private final Method method;
    private final ArthasHttpRequest request;

    public ArthasHttpRequestSerializeHandler(Method method, ArthasHttpRequest request) {
        Assert.notNull(method, "Method cannot be null !!!");
        Assert.notNull(request, "ArthasHttpRequest cannot be null !!!");
        this.method = method;
        this.request = request;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object[] handle() throws ArthasException {

        // get method parameters count
        // intialize Object array
        //
        List<MethodParam> params = methodParam(method);
        Object[] target = new Object[params.size()];

        if (params.size() > 0) {
            for (int i = 0; i < params.size(); i++) {

                // get method parameter object from list
                //

                MethodParam param = params.get(i);

                if (param.getIsPath()) {

                    // uri contains parameters
                    //
                    String v = request.getGet().get(param.getName());
                    if (param.getNess() && (null == v || v.length() <= 0)) {
                        throw new ArthasException(Constant.SERI_ERR_CODE, Constant.SERI_NESS_MSG, ArthasException.APP_ERR);
                    }
                    if (null != v && v.length() > 0) {
                        Convertor<?> conv = new PrimitiveConvertor<>(v, param.getClazz());
                        target[i] = conv.convert();
                    }
                } else {

                    // post content contains parameters
                    // convert array to parameter
                    //
                    if (ConvertProcessor.isArray(param.getClazz())) {
                        List<String> v = request.getPost().get(param.getName());
                        if (null != v && v.size() > 0) {
                            Convertor<?> conv = new ArrayConvertor<>(v, param.getClazz());
                            target[i] = conv.convert();
                        }
                    }
                    // convert model to parmeter
                    //
                    else {
                        Convertor conv = new ModelConvertor(request.getPost(), param.getClazz());
                        target[i] = conv.convert();
                    }
                }
            }
        }
        return target;
    }

    private List<MethodParam> methodParam(Method method) {

        // initialize params list, if method is null, return blank list directly
        //
        List<MethodParam> params = new ArrayList<>();
        if (null == method) {
            return params;
        }

        Arrays.stream(method.getParameters())
                .forEach(p -> {
                    if (null != p.getDeclaredAnnotationsByType(PathParam.class) && p.getDeclaredAnnotationsByType(PathParam.class).length > 0) {
                        // add uri parameter
                        params.add(new MethodParam(getAnno(p).name(), getAnno(p).ness(), p.getType()));
                    } else {
                        // add post parameter
                        params.add(new MethodParam(p.getType(), false));
                    }
                });
        return params;
    }

    private PathParam getAnno(Parameter parameter) {
        return parameter.getAnnotation(PathParam.class);
    }

    // inner class
    // model for parameter
    //
    @Data
    @AllArgsConstructor
    private static class MethodParam {
        private String name;
        private Boolean ness;
        private Class<?> clazz;
        private Boolean isPath;

        MethodParam(Class<?> clazz, Boolean isPath) {
            this.clazz = clazz;
            this.isPath = isPath;
        }

        MethodParam(String name, Boolean ness, Class<?> clazz) {
            this.name = name;
            this.ness = ness;
            this.clazz = clazz;
            this.isPath = true;
        }
    }
}
